﻿namespace NTLib.Core.Threading
{
    using Extensions;
    using NTLib.Core.ScopeLock;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;

    /// <summary>
    /// Synchronize multiple thread using tasks
    /// </summary>
    public class AsyncLock
    {
        #region Fields

        private int _lockCounter;
        private readonly Queue<TaskCompletionSource<bool>> _waiters;

        #endregion

        #region Ctor

        /// <summary>
        /// Initializes a new instance of the <see cref="AsyncLock"/> class.
        /// </summary>
        public AsyncLock()
        {
            this._waiters = new Queue<TaskCompletionSource<bool>>();
            this._lockCounter = 0;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Waits the one asynchronous lock.
        /// </summary>
        /// <returns></returns>
        public Task WaitOneAsync(CancellationToken token = default(CancellationToken))
        {
            TaskCompletionSource<bool> waiter = null;
            lock (_waiters)
            {
                if (_waiters.Count > 0 || this._lockCounter > 0)
                {
                    this._lockCounter++;
                    waiter = new TaskCompletionSource<bool>();
                    _waiters.Enqueue(waiter);
                }
                else
                {
                    this._lockCounter++;
                    return Task.FromResult<bool>(true);
                }
            }
            if (token != default(CancellationToken))
            {
                Task.Run(() =>
                {
                    while (!waiter.Task.IsCompleted ||
                           !waiter.Task.IsCanceled ||
                            !waiter.Task.IsFaulted)
                    {
                        if (token.IsCancellationRequested)
                        {
                            if (waiter.TrySetCanceled())
                            {
                                lock (_waiters)
                                {
                                    var waiters = _waiters.Where(w => w != waiter).ToArray();
                                    _waiters.SafeClear();
                                    foreach (var elem in waiters)
                                        _waiters.Enqueue(elem);
                                    this._lockCounter--;
                                }
                            }
                            break;
                        }
                        Task.Delay(2).Wait();
                    }
                });
            }
            return waiter.Task;
        }

        /// <summary>
        /// Releases one waiter
        /// </summary>
        public void ReleaseOne()
        {
            TaskCompletionSource<bool> waiter = null;
            lock (_waiters)
            {
                this._lockCounter--;
                if (_waiters.Count > 0)
                    waiter = _waiters.Dequeue();
            }

            if (waiter != null)
                waiter.SetResult(true);
        }

        /// <summary>
        /// Return a scope lock item to lock the AsyncLock with the using pattern
        /// </summary>
        /// <returns></returns>
        public async Task<ScopeLockItem<AsyncLock>> LockAsync(CancellationToken token = default(CancellationToken))
        {
            await WaitOneAsync(token);
            return new ScopeLockItem<AsyncLock>(this, (o) => o.ReleaseOne());
        }

        #endregion

    }
}
